// select服务器 - 线性检测和通信，当客户端较多时效率低
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>

int main()
{
    // 1.创建监听的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("socket");
        return -1;
    }

    // 2.绑定本地的IP port
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8003);
    saddr.sin_addr.s_addr = INADDR_ANY; // 0 = 0.0.0.0 -> 读取本地的ip地址
    int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));
    if (ret == -1)
    {
        perror("bind");
        return -1;
    }

    // 3.给创建的套接字设置监听
    ret = listen(fd, 128);
    if (ret == -1)
    {
        perror("listen");
        return -1;
    }

    // 4.创建文件描述符集合，用以存储需要检测读事件的所有文件描述符
    fd_set redset; // 读集合
    FD_ZERO(&redset);
    FD_SET(fd, &redset);

    int maxfd = fd;
    // 5.循环调用select() -> 周期性检测所有文件描述符
    while(1)
    {
        fd_set tmp = redset;
        int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
        // 判断是否是监听的fd
        if (FD_ISSET(fd, &tmp))
        {
            // 接受客户端的连接
            int cfd = accept(fd, NULL, NULL);
            FD_SET(cfd, &redset);
            maxfd = cfd > maxfd ? cfd : maxfd;
        }
        for (int i = 0; i <= maxfd; i++)
        {
            if (i != fd && FD_ISSET(i, &tmp))
            {
                // 接收数据
                char buff[1024];
                int len = recv(i, buff, sizeof(buff), 0);
                if (len > 0)
                {
                    printf("client say: %s\n", buff);
                    ret = send(i, buff, len, 0);
                    if (ret == -1)
                    {
                        perror("send error");
                        break;
                    }
                }
                else if (len == 0)
                {
                    printf("客户端已经断开了连接...\n");
                    FD_CLR(i, &redset);
                    close(i);
                    break;
                }
                else
                {
                    perror("recv");
                    break;
                }
            }
        }
    }

    // 8.关闭文件描述符
    close(fd);

    return 0;
}